package com.ctsi.commons.util.id;

import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListSet;

/**
 * 产生随机字符串,线程不安全,但是如果构造后,立即执行一次,并且不修改属性,就是安全的
 * 
 * @author zhangjw
 * 
 */
public class RandomStrg {

	private boolean allchars = false;

	private int length = 8;

	private Set<String> rset;

	private List<Character> lower = null;

	private List<Character> upper = null;

	private char[] single = null;

	private int singlecount = 0;

	private boolean singles = false;

	private String algorithm = null;

	private String provider = null;

	private boolean secure = false;

	private Random random = null;
	private Random random2 = null;

	public RandomStrg() {
		setCharset("a-z0-9");
	}

	public RandomStrg(int length) {
		this();
		setLength(length);
	}

	private int count;

	private final float getFloat() {
		if (secure) {
			return random.nextFloat();
		}
		count++;
		if ((count & 1) == 0) {
			return random2.nextFloat();
		} else {
			return random.nextFloat();
		}

	}

	/**
	 * generate the Random object that will be used for this random number generator
	 * 
	 */
	private final void generateRandomObject() {

		if (random == null) {
			if (secure) {
				try {
					// get an instance of a SecureRandom object
					if (provider != null)
						// search for algorithm in package provider
						random = SecureRandom.getInstance(algorithm, provider);
					else
						random = SecureRandom.getInstance(algorithm);
					random2 = random;
				} catch (NoSuchAlgorithmException ne) {
					throw new RuntimeException(ne.getMessage());
				} catch (NoSuchProviderException pe) {
					throw new RuntimeException(pe.getMessage());
				}
			} else {
				// 这样jdk14就没有问题了

				random = new Random();

				random2 = new MTRandom();

			}
		}

	}

	/**
	 * generate the random string
	 * 
	 */
	private final void generaterandom(StringBuilder randomstr) {

		if (allchars)
			for (int i = 0; i < length; i++)
				randomstr.append(Character.valueOf((char) (34 + ((int) (getFloat() * 93)))).toString());
		else if (singles) {
			// check if there are single chars to be included

			if (upper.size() == 3) {
				// check for the number of ranges max 3 uppercase lowercase
				// digits

				// build the random string
				for (int i = 0; i < length; i++) {
					// you have four groups to choose a random number from, to
					// make
					// the choice a little more random select a number out of
					// 100

					// get a random number even or odd
					if (((int) (getFloat() * 100)) % 2 == 0) {

						// the number was even get another number even or odd
						if (((int) (getFloat() * 100)) % 2 == 0)
							// choose a random char from the single char group
							randomstr.append(randomSingle().toString());
						else
							// get a random char from the first range
							randomstr.append(randomChar(lower.get(2), upper.get(2)).toString());
					} else {
						// the number was odd

						if (((int) (getFloat() * 100)) % 2 == 0)
							// choose a random char from the second range
							randomstr.append(randomChar(lower.get(1), upper.get(1)).toString());
						else
							// choose a random char from the third range
							randomstr.append(randomChar(lower.get(0), upper.get(0)).toString());
					}
				}
			} else if (upper.size() == 2) {
				// single chars are to be included choose a random char from
				// two different ranges

				// build the random char from single chars and two ranges
				for (int i = 0; i < length; i++) {
					// select the single chars or a range to get each random
					// char
					// from

					if (((int) (getFloat() * 100)) % 2 == 0) {

						// get random char from the single chars
						randomstr.append(randomSingle().toString());
					} else if (((int) (getFloat() * 100)) % 2 == 0) {

						// get the random char from the first range
						randomstr.append(randomChar(lower.get(1), upper.get(1)).toString());
					} else {

						// get the random char from the second range
						randomstr.append(randomChar(lower.get(0), upper.get(0)).toString());
					}
				}
			} else if (upper.size() == 1) {

				// build the random string from single chars and one range
				for (int i = 0; i < length; i++) {
					if (((int) getFloat() * 100) % 2 == 0)
						// get a random single char
						randomstr.append(randomSingle().toString());
					else
						// get a random char from the range
						randomstr.append(randomChar(lower.get(0), upper.get(0)).toString());
				}
			} else {
				// build the rand string from single chars
				for (int i = 0; i < length; i++)
					randomstr.append(randomSingle().toString());
			}
		} else {

			// no single chars are to be included in the random string
			if (upper.size() == 3) {

				// build random strng from three ranges
				for (int i = 0; i < length; i++) {

					if (((int) (getFloat() * 100)) % 2 == 0) {

						// get random char from first range
						randomstr.append(randomChar(lower.get(2), upper.get(2)).toString());
					} else if (((int) (getFloat() * 100)) % 2 == 0) {

						// get random char form second range
						randomstr.append(randomChar(lower.get(1), upper.get(1)).toString());
					} else {

						// get random char from third range
						randomstr.append(randomChar(lower.get(0), upper.get(0)).toString());
					}
				}
			} else if (upper.size() == 2) {

				// build random string from two ranges
				for (int i = 0; i < length; i++) {
					if (((int) (getFloat() * 100)) % 2 == 0)
						// get random char from first range
						randomstr.append(randomChar(lower.get(1), upper.get(1)).toString());
					else
						// get random char from second range
						randomstr.append(randomChar(lower.get(0), upper.get(0)).toString());
				}
			} else

				// build random string
				for (int i = 0; i < length; i++)
					// get random char from only range
					randomstr.append(randomChar(lower.get(0), upper.get(0)).toString());
		}
	}

	/**
	 * 产生一个随机字符
	 * 
	 * @returns - 一个随机字符
	 * 
	 */
	private final Character randomSingle() {

		return Character.valueOf(single[(int) ((getFloat() * singlecount) - 1)]);
	}

	/**
	 * generate a random character
	 * 
	 * @param lower
	 *            lower bound from which to get a random char
	 * @param upper
	 *            upper bound from which to get a random char
	 * 
	 * @returns - a randomly generated character
	 * 
	 */
	private final Character randomChar(Character lower, Character upper) {
		int tempval;
		char low = lower.charValue();
		char up = upper.charValue();

		// get a random number in the range lowlow - lowup
		tempval = (int) ((int) low + (getFloat() * (up - low)));

		// return the random char
		return Character.valueOf((char) tempval);
	}

	public final String getRandom() {
		StringBuilder randomstr = new StringBuilder(40);
		getRandom(randomstr);
		return randomstr.toString();
	}

	/**
	 * 获取随即字符串
	 * 
	 * 
	 * @return - 随机串
	 * 
	 */
	public final void getRandom(StringBuilder randomstr) {
		generateRandomObject();

		generaterandom(randomstr); // generate the first random string

		if (rset != null) {
			String result = randomstr.toString();
			while (rset.contains(result)) {
				randomstr.setLength(0);
				generaterandom(randomstr);
				result = randomstr.toString();
			}

			rset.add(result); // add the new random string
		}

	}

	/**
	 * set the ranges from which to choose the characters for the random string
	 * 
	 * @param low
	 *            set of lower ranges
	 * @param up
	 *            set of upper ranges
	 * 
	 */
	public final void setRanges(List<Character> low, List<Character> up) {
		lower = low;
		upper = up;
	}

	/**
	 * 设置防止数据重复的,保存容器
	 * 
	 * @param check
	 *            一个set
	 * 
	 */
	public final void setHset(boolean check) {
		if (check)
			this.rset = new ConcurrentSkipListSet<String>();
	}

	/**
	 * 设置随机字符串长度
	 * 
	 * @param len
	 *            长度
	 * 
	 */
	public final void setLength(int len) {
		if (len < 1)
			return;
		length = len;

	}

	/**
	 * 设置加密算法 IBM jdk bug ，基本不能用
	 * 
	 * @param value
	 */
	public final void setAlgorithm(String value) {
		algorithm = value;
		secure = true; // a SecureRandom object is to be used
	}

	/**
	 * set the provider name
	 * 
	 * @param value
	 *            name of the package to check for the algorithm
	 * 
	 */
	public final void setProvider(String value) {
		provider = value;
	}

	/**
	 * set the allchars flag
	 * 
	 * @param value
	 *            boolean value of the allchars flag
	 * 
	 */
	public final void setAllchars(boolean value) {
		allchars = value;
	}

	/**
	 * set the array of single chars to choose from for this random string and the number of chars in the array
	 * 
	 * @param chars
	 *            the array of single chars
	 * @param value
	 *            the number of single chars
	 * 
	 */
	public final void setSingle(char[] chars, int value) {
		single = chars; // set the array of chars
		singlecount = value; // set the number of chars in array single
		singles = true; // set flag that single chars are in use
	}

	public final void setCharset(String value) {
		// values tells the method whether or not to check for single chars
		boolean more = true;

		// create the arraylists to hold the upper and lower bounds for the char
		// ranges
		lower = new ArrayList<Character>(3);
		upper = new ArrayList<Character>(3);

		// user has chosen to use all possible characters in the random string
		if ("all".equalsIgnoreCase(value)) {
			allchars = true; // set allchars flag
			// all chars are to be used so there are no single chars to sort
			// through
			more = false;
		} else if ((value.charAt(1) == '-') && (value.charAt(0) != '\\')) {
			// run through the ranges at most 3
			while (more && (value.charAt(1) == '-')) {

				// check to make sure that the dash is not the single char
				if (value.charAt(0) == '\\')
					break;
				else {
					// add upper and lower ranges to there list
					lower.add(Character.valueOf(value.charAt(0)));
					upper.add(Character.valueOf(value.charAt(2)));
				}

				// check to see if there is more to the charset
				if (value.length() <= 3)
					more = false;
				else
					// create a new string so that the next range if there is
					// one
					// starts it
					value = value.substring(3);
			}
		}

		// if more = false there are no single chars in the charset
		if (more) {

			single = new char[30]; // create single

			// create a set of tokens from the string of single chars
			StringTokenizer tokens = new StringTokenizer(value);

			while (tokens.hasMoreTokens()) {
				// get the next token from the string
				String token = tokens.nextToken();

				if (token.length() > 1)
					// char is a - add it to the list
					single[singlecount++] = '-';

				// add the current char to the list
				single[singlecount++] = token.charAt(0);
			}
		}
		if ((lower == null) && (single == null))
			setCharset("a-zA-Z0-9");
	}
}
